package com.kungfu.core;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Date;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;

import com.alibaba.fastjson.JSON;
import com.kungfu.common.ApiException;
import com.kungfu.common.CommonResult;
import com.kungfu.common.JsonUtil;
import com.kungfu.common.ResultCodeEnum;
import com.kungfu.core.ApiStore.ApiRunnable;
import com.kungfu.service.impl.UserServiceImpl;

public class ApiGatewayHandler implements InitializingBean , ApplicationContextAware{
	private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
	
	private static final String METHOD = "method";
	private static final String PARAMS = "params";
	
	final ParameterNameDiscoverer parameterUtil;
	
	private ApiStore apiStore;
	
	
	
	public ApiGatewayHandler(){
		parameterUtil = new LocalVariableTableParameterNameDiscoverer();
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		apiStore = new ApiStore(applicationContext);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		apiStore.loadApiFromSpringBeans();
	}

	public void handle(HttpServletRequest req, HttpServletResponse resp) {
		String params = req.getParameter(PARAMS);
		String method = req.getParameter(METHOD);
		ApiRunnable apiRun = null;
		CommonResult result = null; 
		try {
			apiRun = sysParamsValidate(req);
			logger.info("请求接口 = [{}] , 参数 = {}", method , params);
			Object [] args = buildparams(apiRun , params,req,resp);
			Object rst = apiRun.run(args);
			result = CommonResult.build(ResultCodeEnum.SUCCESS, rst);
		} catch (ApiException e) {
			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			logger.error("调用失败 ,api={} , params={} ,异常信息={}", method , params,e.getMessage());
			result = handleError(e);
		} catch (InvocationTargetException e) {
			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			logger.error("调用失败 ,api={{}} , params = {} ,异常信息={}", method , params,e.getTargetException());
			result = handleError(e.getTargetException());
		} catch(Exception e){
			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			logger.error("其它异常", e);
			result = handleError(e);
		}
		returnResult(result , resp);
	}

	private void returnResult(Object result, HttpServletResponse resp) {
		try {
			resp.setCharacterEncoding("UTF-8");
			resp.setContentType("text/html/json;charset=utf-8");
			resp.setHeader("Pragma", "no-cache");
			resp.setDateHeader("Expires", 0);
			resp.getWriter().write(JSON.toJSONString(result));
		} catch (IOException e) {
			logger.error("服务器中心响应异常",e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 验证业务参数和构建业务参数对象
	 * @param api
	 * @param jsonParam
	 * @param req
	 * @param resp
	 * @return
	 * @throws ApiException
	 */
	private Object[] buildparams(ApiRunnable api, String jsonParam, HttpServletRequest req, HttpServletResponse resp) throws ApiException {
		Map<String,Object> map = null;
		try{
			map = JsonUtil.toMap(jsonParam);
		} catch(Exception e){
			throw new ApiException("调用失败：json字符串格式异常,请检查 [params] 参数");
		}
		if(map == null){
			map = new HashMap<>();
		}
		
		Method method = api.getTargetMethod();
		List<String> paramNames = Arrays.asList(parameterUtil.getParameterNames(method));
		
		Class<?>[] paramTypes = method.getParameterTypes(); //反射
		for(Map.Entry<String, Object> m : map.entrySet()){
			if(!paramNames.contains(m.getKey())){
				throw new ApiException("调用失败：不存在的参数 , 参数名为："+m.getKey());
			}
		}
		
		Object[] args = new Object[paramTypes.length];
		for (int i = 0; i < paramTypes.length; i++) {
			if(paramTypes[i].isAssignableFrom(HttpServletRequest.class)){
				args[i] = req;
			} else if(map.containsKey(paramNames.get(i))){
				try {
					args[i] = convertJsonToBean(map.get(paramNames.get(i)),paramTypes[i]);
				} catch (Exception e) {
					throw new ApiException("调用失败：指定参数格式错误或值错误 ， 参数名为："+paramNames.get(i));
				}
			} else {
				args[i] = null;
			}
		}
		return args;
	}
	
	/**
	 * 将Map转换成具体的目标方法参数对象
	 * @param object
	 * @param targetClass
	 * @return
	 * @throws Exception
	 */
	private <T>Object convertJsonToBean(Object val, Class<?> targetClass) throws Exception{
		Object result = null;
		if(val == null){
			return null;
		} else if(Integer.class.equals(targetClass)){
			result = Integer.parseInt(val.toString());
		} else if(Long.class.equals(targetClass)){
			result = Long.parseLong(val.toString());
		} else if(Date.class.equals(targetClass)){
			if(val.toString().matches("[0-9]+")){
				result = new Date(Long.parseLong(val.toString()));
			} else {
				throw new IllegalArgumentException("日期必须是长整型的时间戳");
			}
		} else if(String.class.equals(targetClass)){
			if(val instanceof String){
				result = val;
			} else {
				throw new IllegalArgumentException("转换目标类型为字符串");
			}
		} else {
			result = JsonUtil.convertValue(val,targetClass);
		}
		return result;
	}

	private CommonResult handleError(Throwable throwable) {
		String code = "";
		String message = "";
		if(throwable instanceof ApiException){
			code = "400001";
		} else {
			code = "500";
		}
		message = throwable.getMessage();
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		PrintStream stream = new PrintStream(out);
		throwable.printStackTrace(stream);
		return CommonResult.buildErrResult(code, message,out.toString());
	}


	private ApiRunnable sysParamsValidate(HttpServletRequest req) throws ApiException{
		String json = req.getParameter(PARAMS);
		String apiName = req.getParameter(METHOD);
		ApiRunnable api = null;
		if(apiName == null || apiName.trim().equals("")) {
			throw new ApiException("调用失败,参数 [method] 为空 " );
		} else if((api = apiStore.findApiRunnable(apiName)) == null){
			throw new ApiException("调用失败,指定API不存在 , API: "+apiName );
		} else if(json == null || json.trim().equals("")){
			throw new ApiException("调用失败,参数 [params] 为空 " );
		} 
		return api;
	}
}
